/*1.GMP模型原理，包括work stealing机制、hand off机制（同步系统调用）、netpoller
G:Goroutine
Go协程
M:Machine Thread
一种系统线程的抽象模型，可以理解为系统线程
P:Processor
虚拟处理器，默认为CPU核心数,可以大于CPU核心数，但同一时间最多只有CPU核心数的P运行

每个Go的函数可以生成一个Go协程，相当于Go的一个任务，该Go协程也有自己的栈、状态等数据，大小2-4k；
每个P有自己的Go协程队列，称为该P的LRQ（本地队列），其运行时会与一个M进行绑定，Go协程实际运行在M上；
每个活动的M会对应在某个CPU核心上运行；
除了LRQ外，还有不属于任何一个P的GRQ；

加入：
	每当创建一个Go协程后，它会尝试加入某个LRQ，如果LRQ已满则会进入GRQ；
执行：
	每个P执行时会绑定一个M，该M
		会从P的LRQ中尝试获取一个Go协程执行，
		如果该P对应的LRQ队列为空，会从GRQ队列中加载一个Go协程执行，
		如果GRQ队列也没有可执行的Goroutine，则会从netpoller中加载一个就绪的Goroutine执行，
		如果netpoller中也找不到可执行的Goroutine，则会从其他P的队列中偷取一半的Goroutine执行，这就是work stealing机制
	如果M中有Goroutine正常执行，则
		该Goroutine在M所对应的时间片内
			正常执行完，M从P的LRQ中读取下一个Goroutine执行，同时与上一个Goroutine解绑
			或该Goroutine在执行时主动让出执行权，其与M解绑，加入GRQ，M读取下一个Goroutine执行，
			或该Goroutine在执行时因channel、互斥锁进入阻塞态，其与M解绑，M读取下一个Goroutine执行，如果其被其他Goroutine唤醒，则尝试加入唤醒者所属P的LRQ，如果该LRQ已满，其会带走该LRQ一半的Goroutine进入GRQ
		时间片外该Goroutine对应M正常进行切换
	如果出现系统调用，
		同步系统调用时
			Goroutine被阻塞同时（线程阻塞非协程阻塞），则其对应M也被阻塞，P与该M解绑同时重新绑定一个新的空闲M，如果没有空闲M则创建一个新的，新M继续执行下一个Goroutine，
			当该M就绪后，此M与G解绑，G会尝试加入一个LRQ中（优先回到原P的LRQ中），如果LRQ已满会加入GRQ，M会进入休眠M队列
			以上就是hand off机制
		异步系统调用时
			发生非阻塞I/O读写的G会与M解绑，由NetPoller接管，M会继续执行下一个G，当被阻塞的G就绪后，在NetPoller中会进入runnable状态，G从NetPoller回到原LRQ或GRQ中。

2.Go语言中通道、类型及特点
Channel
	环形队列、固定大小、先进先出的数组
	nil通道
		var ch chan int 没有被初始化，读、写都会被阻塞，阻塞后无法被解除
	非nil通道
		未关闭
			无缓冲
				除send协程外，要有其他对应的recv协程，否则会死锁，
				send同时会阻塞，需要另外的recv协程接触阻塞，反之亦然，
				cap为0，
				只要recv有其他send协程配合，可以无限遍历,反之亦然
			有缓冲
				cap > 0
				当send完最后一个元素时，再次send会阻塞，当recv装满cap后，再次recv会阻塞，当有空间让出时会解除阻塞
				可以无限遍历，直到阻塞，当有元素新放入时可以继续遍历，主动读走一个返回两个值，true和元素，只要通道未关闭，第一值一直为true


		已关闭
			无缓冲
				同未关闭，但不能写入元素，
				读完后，继续读出，第一值为false，第二值为零值，for遍历完就结束了，不能无限遍历
				不再阻塞
			有缓冲
				同未关闭，但不能写入元素
				不再阻塞，不能无限遍历，读完后，继续读出，第一值为false，第二值为零值
			可以通过第一值判断返回的第二值是否有效
	struct{}型通道
		一种特殊的通道，用来发送信号，不占用内存空间

*/
//3使用Go语言完成WebServer基本功能代码，并能使用浏览器访问到网页。请简述浏览器发起请求到看到网页的整个过程
/*
发起请求：
	访问网页为域名形式
		浏览器所在客户端需进行DNS解析，解析后数据来源优先级（本地缓存》hosts文件指向》DNS响应），
		根据DNS解析结果IP，查找路由表转发数据包（二层根据ARP表封装并转发出接口），
		请求包4层为TCP应用层为HTTP，请求报文头部第一次一般采用GET方法，请求消息包含请求行、请求头部、空行、请求数据，其中GET为请求行中的请求方法字段具体值，
		当请求报文（HTTP层）到达server时，发起请求过程结束
	访问网页为IP地址形式
		无需DNS解析，直接根据路由表转发请求，其他基本内容相同
响应请求：
	服务器需要预先监听80端口，当TCP连接建立成功后，根据就绪连接准备socket对象处理就绪连接，针对每个就绪连接创建一个socket，
	解析请求报文，根据请求报文的请求行、请求头，进行初步解析，根据请求数据进行下一步解析处理，
	如果请求的资源为静态资源，则交由静态service处理，如nginx、apache，这些数据是已经存储在磁盘上的，不需要程序计算后返回加工后的数据
	如果请求的资源为动态资源，则交由动态service处理，如tomcat、其他处理程序，其将原始数据经过一定运算后返回的数据填入响应报文中
	结合响应数据构建响应报文，响应报文包含状态行、消息报头、空行、响应数据
	服务器将响应报文通过socket发出，通过路由查找返回给客户端，这其中还有些复杂的数据包处理过程，省略
	客户端收到响应报文后，通过TCPack进行确认，完成后断开此次连接
	HTTP是无状态、无连接的，单次请求、单次响应
处理响应：
	客户端收到响应报文后，浏览器对响应报文进行解析，之后渲染引擎进行渲染，将处理后的数据展示至屏幕上，如果有js脚本的话，js引擎也会运行它
	这其中也存在其他复杂处理过程，省略
*/

package main

import (
	"fmt"
	"log"
	"net"
	"strings"
)

const body = `
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>title</title>
</head>
<body>
    <h1 style="color:blue">The page is test for this program.</h1>
</body>
</html>
`

const header = `
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: %d
X-Server: abcd.com
Connection: keep-alive

%s`

var response = strings.ReplaceAll(
	fmt.Sprintf(header, len(body), body), "\n", "\r\n",
)

func main() {
	laddr, err := net.ResolveTCPAddr("tcp4", "0.0.0.0:9999") //解析地址
	if err != nil {
		log.Fatal(err) //程序退出
	}
	server, err := net.ListenTCP("tcp4", laddr)
	if err != nil {
		log.Println(err)
		return
	}
	defer server.Close() //保证一定关闭
	for {
		conn, err := server.Accept() //接收连接，分配socket
		if err != nil {
			log.Println(err)
		}

		go func(conn net.Conn) {
			defer conn.Close()         //保证一定关闭
			data := make([]byte, 4096) //设置缓冲区
			n, err := conn.Read(data)  //成功返回接收了多少字节
			if err != nil {
				fmt.Println(err)
				return
			}
			if n == 0 {
				log.Println("用户", conn.RemoteAddr(), "主动断开")
				return
			}
			user_sent := data[:n] //请求报文解析等
			//这里可以加判断解析请求，根据不同的请求，进行回复
			fmt.Println("The strings send by user is ", string(user_sent))
			conn.Write([]byte(response))
		}(conn)
	}

}
